﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.Language.StandardClassification;

// this implements an aggreator factory for the TaggerProvider(s)


namespace PostscriptTools.Classifier {
	[Export(typeof(ITaggerProvider))] // this means that this class will implement the ITaggerProvider interface
	[ContentType("postscript")]
	[TagType(typeof(ClassificationTag))]
	internal sealed class PostscriptClassifierProvider : ITaggerProvider {
		[Import]
		internal IClassificationTypeRegistryService ClassificationTypeRegistry = null;

		[Import]
		internal IBufferTagAggregatorFactoryService AggregatorFactory = null;

		public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag {
			// create a single tagger for each buffer.
			// the delegate is the function used when the property needs to be created
			Func<ITagger<T>> createAPostscriptClassifier = delegate() {
			                      		return new PostscriptClassifier(buffer, this.AggregatorFactory, ClassificationTypeRegistry) as ITagger<T>;
			                      };
			return buffer.Properties.GetOrCreateSingletonProperty<ITagger<T>>(createAPostscriptClassifier);
		}
	}

	/// <summary>
	/// Classifier that classifies all text as an instance of the OrdinaryClassifierType
	/// </summary>
	internal sealed class PostscriptClassifier : ITagger<ClassificationTag> {
		readonly ITextBuffer _buffer;
		readonly ITagAggregator<PostscriptTokenTag> _aggregator;
		readonly IDictionary<PostscriptToken, IClassificationType> _postscriptTypes;

		internal PostscriptClassifier(ITextBuffer buffer, IBufferTagAggregatorFactoryService aggregatorFactory, IClassificationTypeRegistryService typeService) {
			_buffer = buffer;
			_aggregator = aggregatorFactory.CreateTagAggregator<PostscriptTokenTag>(buffer);
			_aggregator.TagsChanged += new EventHandler<TagsChangedEventArgs>(AggregatorTagsChanged);

			// create mapping from PostscriptToken to classification types
			this._postscriptTypes = new Dictionary<PostscriptToken, IClassificationType>();
			this._postscriptTypes[PostscriptToken.PsOperator] = typeService.GetClassificationType(PredefinedClassificationTypeNames.Operator);
			this._postscriptTypes[PostscriptToken.PsComment] = typeService.GetClassificationType(PredefinedClassificationTypeNames.Comment);
			this._postscriptTypes[PostscriptToken.PsString] = typeService.GetClassificationType(PredefinedClassificationTypeNames.String);
			this._postscriptTypes[PostscriptToken.PsHexString] = typeService.GetClassificationType("postscript.hexstring");
			this._postscriptTypes[PostscriptToken.PsAscii85String] = typeService.GetClassificationType("postscript.ascii85string");
			this._postscriptTypes[PostscriptToken.PsNumber] = typeService.GetClassificationType(PredefinedClassificationTypeNames.Number);
			this._postscriptTypes[PostscriptToken.PsName] = typeService.GetClassificationType(PredefinedClassificationTypeNames.Identifier);
			this._postscriptTypes[PostscriptToken.PsImmediateName] = typeService.GetClassificationType("postscript.immediatename");
			this._postscriptTypes[PostscriptToken.PsLiteralName] = typeService.GetClassificationType("postscript.literalname");
			this._postscriptTypes[PostscriptToken.PsProcOpenOperator] = typeService.GetClassificationType("postscript.procoperator");
			this._postscriptTypes[PostscriptToken.PsProcCloseOperator] = typeService.GetClassificationType("postscript.procoperator");
			this._postscriptTypes[PostscriptToken.PsDictOpenOperator] = typeService.GetClassificationType("postscript.dictoperator");
			this._postscriptTypes[PostscriptToken.PsDictCloseOperator] = typeService.GetClassificationType("postscript.dictoperator");
			this._postscriptTypes[PostscriptToken.PsArrayOpenOperator] = typeService.GetClassificationType("postscript.arrayoperator");
			this._postscriptTypes[PostscriptToken.PsArrayCloseOperator] = typeService.GetClassificationType("postscript.arrayoperator");
			this._postscriptTypes[PostscriptToken.PsPreprocessorCommand] = typeService.GetClassificationType(PredefinedClassificationTypeNames.PreprocessorKeyword);
			this._postscriptTypes[PostscriptToken.PsPreprocessorComment] = typeService.GetClassificationType(PredefinedClassificationTypeNames.Comment);
			this._postscriptTypes[PostscriptToken.PsBinary] = typeService.GetClassificationType("postscript.binary");
			this._postscriptTypes[PostscriptToken.PsError] = typeService.GetClassificationType("postscript.error");
		}

		void AggregatorTagsChanged(object sender, TagsChangedEventArgs e) {
			var temp = this.TagsChanged;
			if (temp != null) {
				NormalizedSnapshotSpanCollection spans = e.Span.GetSpans(_buffer.CurrentSnapshot);
				if (spans.Count > 0) {
					var span = new SnapshotSpan(spans[0].Start, spans[spans.Count - 1].End);
					temp(this, new SnapshotSpanEventArgs(span));
				}
			}
		}

		public event EventHandler<SnapshotSpanEventArgs> TagsChanged;


		/// <summary>
		/// GetTags:   Translate each PostscriptTokenTag to an appropriate ClassificationTag
		/// </summary>
		public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans) {
			foreach (var tagSpan in this._aggregator.GetTags(spans)) {
				var tagSpans = tagSpan.Span.GetSpans(spans[0].Snapshot);
				switch (tagSpan.Tag.Type) {
					case PostscriptToken.PsError:
						break;
					default:
						if (this._postscriptTypes.ContainsKey(tagSpan.Tag.Type)) {
							yield return new TagSpan<ClassificationTag>(tagSpans[0], new ClassificationTag(this._postscriptTypes[tagSpan.Tag.Type]));
						}
						break;
				}
			}
		}
	}
}
